package com.dss.auto_deploy.util;

import cn.hutool.core.collection.CollectionUtil;
import com.dss.auto_deploy.constants.AutoConstants;
import com.dss.auto_deploy.domain.AdmDocker;
import com.dss.auto_deploy.domain.AdmDockerDeploy;
import com.dss.auto_deploy.domain.Deploy;
import com.dss.auto_deploy.domain.Server;
import com.dss.auto_deploy.enums.BooleanTypeEnum;
import com.dss.auto_deploy.service.IServerService;
import com.jcraft.jsch.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.*;
import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @description: 远程ssh跑龙套
 * @author: dongshoushan
 * @date:2022-11-23 20:56:02
 */
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
public class RemoteSshUtil {
    @Lazy
    private final IServerService serverService;

    /**
     * 清空部署日志
     *
     * @throws IOException
     */
    public void recreateFile() throws IOException {
        File file = new File(AutoConstants.DEPLOY_INFO_PATH);
        File file2 = new File(AutoConstants.DEPLOY_INFO_PATH_PREFIX);
        if (file.exists()) {
            file.delete();
        }
        FileUtils.deleteAllFile(AutoConstants.DEPLOY_INFO_PATH_PREFIX);
        file2.mkdirs();
        file.createNewFile();
    }

    /**
     * 清空docker部署日志
     *
     * @throws IOException
     */
    public void recreateDockerFile() throws IOException {
        File file = new File(AutoConstants.DOCKER_DEPLOY_INFO_PATH);
        File file2 = new File(AutoConstants.DOCKER_DEPLOY_INFO_PATH_PREFIX);
        if (file.exists()) {
            file.delete();
        }
        FileUtils.deleteAllFile(AutoConstants.DOCKER_DEPLOY_INFO_PATH_PREFIX);
        file2.mkdirs();
        file.createNewFile();
    }

    /**
     * 登录远程Linux主机
     *
     * @return 是否登录成功
     */


    private Session getSession(String host, int port, String uesename)
            throws Exception {
        JSch jsch = new JSch();
        Session session = jsch.getSession(uesename, host, port);

        return session;
    }

    public Session connect(String host, String uesename,
                           String password, int port) throws Exception {
        Session session = getSession(host, port, uesename);
        session.setPassword(password);
        session.setTimeout(2000);
        Properties config = new Properties();
        config.setProperty("StrictHostKeyChecking", "no");
        session.setConfig(config);
        session.connect();

        return session;
    }

    public Session connect(Server server) throws Exception {
        Session session = getSession(server.getIp(), server.getPort(), server.getUsername());
        session.setPassword(server.getPassword());
        Properties config = new Properties();
        config.setProperty("StrictHostKeyChecking", "no");
        session.setConfig(config);
        session.setTimeout(2000);
        session.connect();
        return session;
    }
    public Session connect(AdmDocker docker) throws Exception {
        Server server = serverService.getServerById(docker.getServerId());
        Session session = getSession(server.getIp(), server.getPort(), server.getUsername());
        session.setPassword(server.getPassword());
        Properties config = new Properties();
        config.setProperty("StrictHostKeyChecking", "no");
        session.setConfig(config);
        session.connect();
        return session;
    }


    public String exec(String command, Session session)
            throws Exception {
        if (session == null) {
            throw new RuntimeException("Session is null!");
        }

        ChannelExec exec = (ChannelExec) session.openChannel("exec");
        InputStream in = exec.getInputStream();
        byte[] b = new byte[1024];

        exec.setCommand(command);
        exec.connect();

        StringBuffer buffer = new StringBuffer();
        StringBuilder builder = processStdout(in);
        while (in.read(b) > 0) {
            buffer.append(new String(b));
        }
        exec.disconnect();
        return builder.toString();
    }
    public  void exec(String command, Session session, Function<String, Boolean> logHandler)
            throws Exception {
        if (session == null) {
            throw new RuntimeException("Session is null!");
        }

        ChannelExec exec = (ChannelExec) session.openChannel("exec");
        InputStream in = exec.getInputStream();
        Scanner sc = new Scanner(in, "UTF-8");
        exec.setCommand(command);
        exec.connect();
        while (sc.hasNextLine()) {//按行来读取日志
            String line = sc.nextLine();
            if (!logHandler.apply(line)) {
                break;
            }
        }
        exec.disconnect();
    }

    /**
     * 解析流获取字符串信息
     *
     * @param in 输入流对象
     * @return 脚本输出结果
     */
    public StringBuilder processStdout(InputStream in) {
        byte[] buf = new byte[1024];
        StringBuilder sb = new StringBuilder();
        try {
            int length;
            while ((length = in.read(buf)) != -1) {
                sb.append(new String(buf, 0, length));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb;
    }

    public void close(Session session) {
        if ((session != null) && session.isConnected()) {
            session.disconnect();
        }
    }
    public void close(Channel channel) {
        if ((channel != null) && channel.isConnected()) {
            channel.disconnect();
        }
    }

    public void putJarFile(Session session, Deploy deploy) throws JSchException, SftpException {
        Server server = serverService.getServerById(deploy.getServerId());
        Channel channel = session.openChannel("sftp");
        channel.connect();
        ChannelSftp sftp = (ChannelSftp) channel;
        sftp.cd(deploy.getRemoteTargetDirectory());
        //上传进度监控
        FileProgressMonitor monitor = new FileProgressMonitor(deploy.getId(), server.getIp(), deploy.getModuleName(), "uploadjar");
        sftp.put(deploy.getLocalFile(), deploy.getRemoteTargetDirectory(), monitor);
        sftp.disconnect();
        channel.disconnect();
    }

    public void putJarDockerFile(Session session, AdmDockerDeploy admDockerDeploy, Server dockerServer) throws JSchException, SftpException {
        Channel channel = session.openChannel("sftp");
        channel.connect();
        ChannelSftp sftp = (ChannelSftp) channel;
        createDir(admDockerDeploy.getRemoteTargetDirectory(), dockerServer);
        sftp.cd(admDockerDeploy.getRemoteTargetDirectory());
        //上传进度监控
        DockerFileProgressMonitor monitor = new DockerFileProgressMonitor(admDockerDeploy.getId(), dockerServer.getIp(), admDockerDeploy.getModuleName(), "uploadDockerJar");
        sftp.put(admDockerDeploy.getLocalFile(), admDockerDeploy.getRemoteTargetDirectory(), monitor);
        sftp.disconnect();
        channel.disconnect();
    }

    public void putFile(Session session, String localFile, String remoteTargetDirectory) throws JSchException, SftpException {
        Channel channel = session.openChannel("sftp");
        channel.connect();
        ChannelSftp sftp = (ChannelSftp) channel;
        createDir(remoteTargetDirectory, sftp);
        sftp.cd(remoteTargetDirectory);
        sftp.put(localFile, remoteTargetDirectory);
        sftp.disconnect();
        channel.disconnect();
    }

    public void putFile(Session session, MultipartFile multipartFile, String remoteTargetDirectory, long size, Long id, String table,String ip) throws JSchException, SftpException, IOException {
        InputStream inputStream = multipartFile.getInputStream();
        String originalFilename = multipartFile.getOriginalFilename();
        Channel channel = session.openChannel("sftp");
        channel.connect();
        ChannelSftp sftp = (ChannelSftp) channel;
        createDir(remoteTargetDirectory, sftp);
        sftp.cd(remoteTargetDirectory);
        DockerFileProgressMonitor monitor = new DockerFileProgressMonitor(id, originalFilename, ip,size, table);
        sftp.put(inputStream, originalFilename, monitor);
        sftp.disconnect();
        channel.disconnect();
    }

    /**
     * sftp下载文件
     *
     * @param directory    下载目录
     * @param downloadFile 下载的文件
     * @param saveFile     存在本地的路径
     * @param sftp
     */
    public void download(String directory, String downloadFile, String saveFile, ChannelSftp sftp) {
        FileOutputStream stream = null;
        try {
            log.info("sftp开始下载文件{}/{}到本地{}", directory, downloadFile, saveFile);
            sftp.cd(directory);
            File file = new File(saveFile);
            stream = new FileOutputStream(file);
            sftp.get(downloadFile, stream);
            log.info("sftp已下载文件{}/{}到本地{}", directory, downloadFile, saveFile);
        } catch (Exception e) {
            log.error("sftp下载失败,directory {},downloadFile {},saveFile {},失败原因:{}", directory, downloadFile, saveFile, e.getMessage());
            throw new ServiceException(e.getMessage());
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public void download(String directory, String downloadFile, String saveFile, Server server) {
        FileOutputStream stream = null;
        Channel channel = null;
        Session session = null;
        ChannelSftp sftp = null;
        try {
            session = connect(server);
            channel = session.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            log.info("sftp开始下载文件{}/{}到本地{}", directory, downloadFile, saveFile);
            sftp.cd(directory);
            File file = new File(saveFile);
            stream = new FileOutputStream(file);
            sftp.get(downloadFile, stream);
            log.info("sftp已下载文件{}/{}到本地{}", directory, downloadFile, saveFile);
        } catch (Exception e) {
            log.error("sftp下载失败,directory {},downloadFile {},saveFile {},失败原因:{}", directory, downloadFile, saveFile, e.getMessage());
            throw new ServiceException(e.getMessage());
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            close(session);
            channel.disconnect();
            sftp.disconnect();
        }
    }

    /**
     * 删除文件
     *
     * @param directory
     * @param deleteFile
     * @param sftp
     */
    public void delete(String directory, String deleteFile, ChannelSftp sftp) {
        try {
            sftp.cd(directory);
            sftp.rm(deleteFile);
            System.out.println("删除成功");
        } catch (Exception e) {
            System.out.println("删除失败");
            e.printStackTrace();
        }
    }

    public void delete(String directory, String deleteFile, Server server) {
        Channel channel = null;
        Session session = null;
        ChannelSftp sftp = null;
        try {
            session = connect(server);
            channel = session.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            sftp.cd(directory);
            sftp.rm(deleteFile);
            System.out.println("删除成功");
        } catch (Exception e) {
            System.out.println("删除失败");
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        } finally {
            close(session);
            channel.disconnect();
            sftp.disconnect();
        }
    }

    /**
     * 列出列表下的文件
     *
     * @param directory
     * @return
     */
    public List<FileBean> listFiles(String directory, Server server) {
        Session session = null;
        List<FileBean> list = new ArrayList<>();
        ChannelSftp sftp = null;
        Channel channel = null;
        try {
            session = connect(server);
            channel = session.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            Vector vector = sftp.ls(directory);
            for (Object object : vector) {
                if (object instanceof ChannelSftp.LsEntry) {
                    ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) object;
                    String filename = lsEntry.getFilename();
                    if(filename.equals(".") || filename.equals("..")){
                        continue;
                    }
                    SftpATTRS attrs = lsEntry.getAttrs();
                    Date date = new Date(((long) attrs.getMTime()) * 1000L);
                    String longname = lsEntry.getLongname();
                    String substring = longname.substring(16, 30);
                    String[] s = substring.split(" ");
                    String user = s[0] + "/" + s[s.length-1];
                    FileBean fileBean = FileBean.builder().fileName(filename)
                            .permissionsString(attrs.getPermissionsString())
                            .updateTime(DateUtils.getTime(date))
                            .user(user)
                            .build();
                    if ("/".equals(directory)) {
                        fileBean.setFilePath(directory + filename);
                    } else {
                        fileBean.setFilePath(directory + "/" + filename);
                    }
                    if (attrs.isDir()) {
                        if (filename.length() == 0) {
                            continue;
                        }
                        fileBean.setDir(BooleanTypeEnum.YES.getCode());
                        fileBean.setFileType("文件夹");
                        list.add(fileBean);
                    } else {
                        fileBean.setDir(BooleanTypeEnum.NO.getCode());
                        fileBean.setFileSize(StringUtils.computerUnitConversion(attrs.getSize()));
                        String suffix = "";
                        if (filename.contains(".")) {
                            suffix = filename.substring(filename.lastIndexOf(".") + 1);
                        }
                        fileBean.setFileType(suffix + "文件");
                        list.add(fileBean);
                    }
                }
            }
            list.sort(Comparator.comparing(FileBean::getDir).reversed().thenComparing(FileBean::getFileName));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sftp.disconnect();
            channel.disconnect();
            close(session);
        }
        return list;
    }

    /**
     * 下载文件夹下面的所有文件
     *
     * @param viDirectory
     * @param viSaveDir
     * @return
     */
    public List<String> downloadDirFile(String viDirectory, String viSaveDir, Session session) {
        List<String> nFileNameList = null;
        ChannelSftp sftp = null;
        try {
            Channel channel = session.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            // 3.获取目录下面所有文件
            Vector nVector = sftp.ls(viDirectory);
            // 4.循环遍历文件
            for (Object o : nVector) {
                // 5.进入服务器文件夹
                sftp.cd(viDirectory);
                // 6.实例化文件对象
                String nFileName = o.toString();
                if (!nFileName.contains("csv")) {
                    continue;
                }
                File nFile = new File(viSaveDir + File.separator + nFileName);
                // 7.下载文件
                sftp.get(nFileName, Files.newOutputStream(nFile.toPath()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sftp.disconnect();
        }
        return nFileNameList;
    }

    /**
     * 递归下载
     *
     * @param sftp       sftp公司
     * @param remotePath 远程路径
     * @param localPath  局部路径
     */
    public void download(ChannelSftp sftp, String remotePath, String localPath) {
        try {
            // 判断远程文件是目录还是文件
            SftpATTRS attrs = sftp.stat(remotePath);
            if (attrs.isDir()) {
                // 如果是目录，需要创建本地目录
                File localFolder = new File(localPath + "/" + remotePath.substring(remotePath.lastIndexOf("/") + 1));
                FileUtils.createFilePath(localFolder);
                // 迭代目录下的文件和目录
                Vector<ChannelSftp.LsEntry> entries = sftp.ls(remotePath);
                for (ChannelSftp.LsEntry entry : entries) {
                    String entryName = entry.getFilename();
                    if (entryName.equals(".") || entryName.equals("..")) {
                        continue;
                    }
                    download(sftp, remotePath + "/" + entryName, localPath + "/" + remotePath.substring(remotePath.lastIndexOf("/") + 1));
                }
            } else {
                // 如果是文件，开始下载
                sftp.get(remotePath, localPath + "/" + remotePath.substring(remotePath.lastIndexOf("/") + 1));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * zip下载
     *
     * @param sftp       sftp公司
     * @param remotePaths 远程路径
     * @param localPath  局部路径
     * @throws SftpException sftp异常
     * @throws IOException   IOException
     */
    public void zipDirectories(ChannelSftp sftp, String remotePaths, String localPath) throws SftpException, IOException {
        // 创建ZIP输出流
        String zipFileName = localPath + ".zip";
        FileUtils.createFile(zipFileName);
        FileOutputStream fileOutputStream = new FileOutputStream(zipFileName);
        ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);

        // 迭代多个目录
        String[] remotePathArray = remotePaths.split(",");
        for (String remotePath : remotePathArray) {
            zipDirectory(sftp, remotePath.trim(), zipOutputStream);
        }

        // 关闭输出流
        zipOutputStream.close();
        fileOutputStream.close();

        // 下载ZIP文件
        sftp.get(zipFileName, localPath + ".zip");
    }

    /**
     * zip目录
     *
     * @param sftp            sftp公司
     * @param remotePath      远程路径
     * @param zipOutputStream zip输出流
     * @throws SftpException sftp异常
     * @throws IOException   IOException
     */
    public void zipDirectory(ChannelSftp sftp, String remotePath, ZipOutputStream zipOutputStream) throws SftpException, IOException {
        // 获取远程文件夹所有的文件以及文件夹
        Vector<ChannelSftp.LsEntry> fileEntries = sftp.ls(remotePath);
        // 迭代这个文件夹下的所有文件
        for(ChannelSftp.LsEntry entry : fileEntries) {
            String fileName = entry.getFilename();
            if (".".equals(fileName) || "..".equals(fileName)) {
                continue;
            }
            String entryPath = remotePath + "/" + fileName;
            // 如果是文件，压缩文件
            if (!entry.getAttrs().isDir()) {
                SftpATTRS attrs = sftp.stat(entryPath);
                if (!attrs.isDir()) {
                    SftpATTRS attr = sftp.lstat(entryPath);
                    InputStream inputStream = sftp.get(entryPath);
                    ZipEntry zipEntry = new ZipEntry(entryPath.substring(entryPath.lastIndexOf("/") + 1));
                    zipEntry.setTime(attr.getMTime() * 1000L);
                    zipOutputStream.putNextEntry(zipEntry);

                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = inputStream.read(buffer)) > 0) {
                        zipOutputStream.write(buffer, 0, len);
                    }
                    inputStream.close();
                    zipOutputStream.closeEntry();
                }
            } else {
                // 如果是文件夹，创建文件夹并递归压缩
                File folder = new File(entryPath.substring(0, entryPath.lastIndexOf("/")));
                folder.mkdirs();
                zipDirectory(sftp, entryPath, zipOutputStream);
            }
        }
    }

    public static RemoteSshUtil getRemoteSshUtil() {
        Object remoteSshUtil = SpringUtils.getBean("remoteSshUtil");
        return (RemoteSshUtil) remoteSshUtil;
    }

    /**
     * <b>创建一个文件目录</b>
     */
    public void createDir(String createpath, Server server) {
        Session session = null;
        Channel channel = null;
        ChannelSftp sftp = null;
        try {
            session = connect(server);
            channel = session.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            if (isDirExist(createpath, sftp)) {
                sftp.cd(createpath);
                return;
            }
            String[] pathArry = createpath.split("/");
            StringBuffer filePath = new StringBuffer("/");
            for (String path : pathArry) {
                if ("".equals(path)) {
                    continue;
                }
                filePath.append(path).append("/");
                if (isDirExist(filePath.toString(), sftp)) {
                    sftp.cd(filePath.toString());
                } else {
                    // 建立目录
                    sftp.mkdir(filePath.toString());
                    log.info("创建目录成功：{}",filePath);
                    // 进入并设置为当前目录
                    sftp.cd(filePath.toString());
                }
            }
            sftp.cd(createpath);
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            close(session);
            channel.disconnect();
            sftp.disconnect();
        }
    }

    public void createFile(String newFilePath, String fileName, Server server) throws ServiceException {
        Session session = null;
        Channel channel = null;
        ChannelSftp sftp = null;
        try {
            session = connect(server);
            channel = session.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            FileUtils.createFilePath(AutoConstants.EMPTY_PATH);
            FileUtils.createFile(AutoConstants.EMPTY_PATH + fileName);
            List<FileBean> fileBeanList = listFiles(newFilePath, server);
            if (CollectionUtil.isNotEmpty(fileBeanList)) {
                Optional<String> first = fileBeanList.stream().map(FileBean::getFileName).filter(s -> s.equals(fileName)).findFirst();
                if (first.isPresent()) {
                    throw new ServiceException("文件已存在:" + first.get());
                }
            }
            putFile(session, AutoConstants.EMPTY_PATH + fileName, newFilePath);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new ServiceException(e.getMessage());
        } finally {
            close(session);
            assert channel != null;
            channel.disconnect();
            assert sftp != null;
            sftp.disconnect();
        }
    }

    public void createDir(String createpath, ChannelSftp channel) {
        try {
            if (isDirExist(createpath, channel)) {
                channel.cd(createpath);
                return;
            }
            String[] pathArry = createpath.split("/");
            StringBuffer filePath = new StringBuffer("/");
            for (String path : pathArry) {
                if ("".equals(path)) {
                    continue;
                }
                filePath.append(path).append("/");
                if (isDirExist(filePath.toString(), channel)) {
                    channel.cd(filePath.toString());
                } else {
                    // 建立目录
                    channel.mkdir(filePath.toString());
                    // 进入并设置为当前目录
                    channel.cd(filePath.toString());
                }
            }
            channel.cd(createpath);
        } catch (SftpException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * <b>判断目录是否存在</b>
     */
    public  boolean isDirExist(String directory, ChannelSftp channel) {
        boolean isDirExistFlag = false;
        try {
            SftpATTRS sftpATTRS = channel.lstat(directory);
            isDirExistFlag = true;
            return sftpATTRS.isDir();
        } catch (Exception e) {
            if ("no such file".equals(e.getMessage().toLowerCase())) {
                isDirExistFlag = false;
            }
        }
        return isDirExistFlag;
    }

    public void deleteDir(String path, Server server) {
        Session session = null;
        try {
            session = connect(server);
            exec(StringUtils.format("rm -rf {}",path),session);
            System.out.println("删除文件夹成功"+path);
        } catch (Exception e) {
            System.out.println("删除文件夹失败"+path);
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        } finally {
            close(session);
        }
    }

    /**
     * @param server      服务器
     * @param oldFileName 旧文件名称
     * @param newFileName 新文件名字
     * @description: 重命名名称服务器文件
     * @author: dongshoushan
     * @date:2022-11-24 09:24:56
     */
    public void reNameServerFile(Server server,String oldFileName, String newFileName) {
        Channel channel = null;
        Session session = null;
        ChannelSftp sftp = null;
        try {
            session = connect(server);
            channel = session.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            sftp.rename(oldFileName,newFileName);
            System.out.println("修改文件夹成功");
        } catch (Exception e) {
            System.out.println("修改文件夹失败");
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        } finally {
            close(session);
            channel.disconnect();
            sftp.disconnect();
        }
    }


}
